package com.imdroid.carac.services;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import android.util.Log;

import com.baidu.location.BDLocation;
import com.imdroid.carac.IMDroid;
import com.imdroid.carac.activities.TeamMapActivity;
import com.imdroid.carac.audioCodec.UDPSpeaker;
import com.imdroid.carac.model.Team;
import com.imdroid.carac.model.TeamMember;
import com.imdroid.carac.net.ServerAddress;
import com.imdroid.carac.process.PlaySoundProcess;

/**
 * UDP信息接收器
 * 
 */
public class UDPServer {
	private static String TAG = "UDPReceiver";
	/**
	 * 实际处理服务的线程组，管理着一个UDP请求守护线程。
	 */
	private static ExecutorService service;
	private static ExecutorService heartBeatService;
	/**
	 * 服务器工作存根，用于退出服务。
	 */
	private static Future<Void> future;
	/**
	 * HeartBeat工作存根，用于退出服务时的清理
	 */
	private static Future<Void> heartBeatFuture;
	/**
	 * UDP报告文件
	 */
	private static DatagramSocket socket;

	/**
	 * 启动服务
	 * 
	 * @throws Exception
	 */
	public static void startup() throws Exception {
		if (service != null) {
			Log.d(TAG, "Already started ... at port " + socket.getPort());
			return;
		}
		Log.d(TAG, "Server starting ... ");
		System.out.println("Server starting ... ");

		// 启动容纳3个处理信息的线程的线程池
		NotificationGenerator.startupNotificationWorkers(3);

		service = Executors.newSingleThreadExecutor();
		future = service.submit(new Callable<Void>() {
			/*
			 * (non-Javadoc)
			 * 
			 * @see java.util.concurrent.Callable#call()
			 */
			public Void call() throws Exception {
				socket = new DatagramSocket();
				socket.send(getConnectPacket(true)); // 发送信息到服务器，触发连接

				while (true) {
					DatagramPacket packet = new DatagramPacket(new byte[548],
							548);
					socket.receive(packet);
					Log.d(TAG, "packet received ... ");
					NotificationGenerator.tryGenerateNotification(packet);
				}
			}

		});
		heartBeatService = Executors.newSingleThreadExecutor();
		heartBeatFuture = heartBeatService.submit(new Callable<Void>() {
			public Void call() throws Exception {
				while (true) {
					Log.d(TAG, "begin heart beat ... ");
					try {
						Thread.sleep(45000); // 放宽一点，45秒进行一次HeartBeat
					} catch (Exception e) {
						e.printStackTrace();
					}
					sendPacket(getConnectPacket(false));
				}
			}
		});
	}

	/**
	 * Shut down the server
	 */
	public static void shutdown() {
		if (heartBeatService != null && !heartBeatService.isShutdown()) {
			if (heartBeatFuture != null) {
				heartBeatFuture.cancel(true);
				heartBeatFuture = null;
			}
			heartBeatService.shutdown();
			heartBeatService = null;
		}
		if (service != null && !service.isShutdown()) {
			if (future != null) {
				future.cancel(true);
				future = null;
			}
			service.shutdown();
			service = null;
		}
		NotificationGenerator.shutdownNotificationWorkers();
	}

	/**
	 * 连接服务器
	 * 
	 * @param isFirstTime
	 *            if it is the first time
	 * @return the datagram packet return from serer
	 */
	private static DatagramPacket getConnectPacket(boolean isFirstTime) {
		// 这个包信息同时也用作心跳包。
		byte[] data = new byte[15];
		data[0] |= 1 << 4;
		// 表示只发送到服务器的信息
		// 设置信息ID
		System.arraycopy(longToByte(System.currentTimeMillis()), 0, data, 2, 8);
		// 用户号(发送者)
		System.arraycopy(intToByte(IMDroid.getUser().getUser_id()), 0, data,
				10, 4);
		// 设置请求类型
		data[1] = MessageType.CONNECT;
		data[14] = (byte) (isFirstTime ? 1 : 0);
		DatagramPacket packet = new DatagramPacket(data, 15);
		packet.setAddress(ServerAddress.udpAddress);
		packet.setPort(ServerAddress.udpPort);
		return packet;
	}

	/**
	 * Send the packaage
	 * 
	 * @param packet
	 */
	private static void sendPacket(DatagramPacket packet) {
		try {
			socket.send(packet);
		} catch (IOException e) {
			e.printStackTrace();
			// 发送失败，未作处理
		}
	}

	/**
	 * change the into to byte
	 * 
	 * @param data
	 *            the data in integer
	 * @return
	 */
	private static byte[] intToByte(int data) {
		byte[] res = new byte[4];
		res[0] = (byte) ((data >>> 24) & 0xFF);
		res[1] = (byte) ((data >>> 16) & 0xFF);
		res[2] = (byte) ((data >>> 8) & 0xFF);
		res[3] = (byte) (data & 0xFF);
		return res;
	}

	/**
	 * change the data into byte
	 * 
	 * @param data
	 *            the data in long
	 * @return
	 */
	private static byte[] longToByte(long data) {
		byte[] res = new byte[8];
		res[0] = (byte) ((data >>> 56) & 0xFF);
		res[1] = (byte) ((data >>> 48) & 0xFF);
		res[2] = (byte) ((data >>> 40) & 0xFF);
		res[3] = (byte) ((data >>> 32) & 0xFF);
		res[4] = (byte) ((data >>> 24) & 0xFF);
		res[5] = (byte) ((data >>> 16) & 0xFF);
		res[6] = (byte) ((data >>> 8) & 0xFF);
		res[7] = (byte) (data & 0xFF);
		return res;
	}

	/**
	 * @param rawData
	 * @param startIdx
	 * @return
	 */
	private static int byteToInt(byte[] rawData, int startIdx) {
		return (rawData[startIdx] << 24)
				| ((rawData[startIdx + 1] & 0xFF) << 16)
				| ((rawData[startIdx + 2] & 0xFF) << 8)
				| (rawData[startIdx + 3] & 0xFF);
	}

	/**
	 * The constant type of the message
	 * 
	 */
	private final class MessageType {
		// plain message types
		public static final byte CONNECT = 1;
		public static final byte CALL = 2;
		// public static final byte LOCATION = 3;
		public static final byte TEAM_JOIN = 4;
		public static final byte TEAM_CANCEL = 5;
		public static final byte TEAM_CONFIRMED = 6;
		public static final byte TEAM_EXIT = 7;
		public static final byte TEAM_DESTINATION = 8;
		public static final byte DESTINATION = 9;
		public static final byte TAKING_PICTURE = 10;
		// public static final byte DISCONNECT = 11;
		public static final byte TEAM_LOCATION = 12;
		public static final byte REAL_TIME = 13;
		// large file transfer types
		public static final byte IDA_AUDIO = -1;
		public static final byte IMAGE = -2;
	}

	/**
	 * The notification generator
	 * 
	 */
	private static class NotificationGenerator {
		private static final String TAG = "NotificationGenerator";
		/**
		 * 实际执行工作的线程组
		 */
		private static ExecutorService workers = null;

		public static void startupNotificationWorkers(int workerCount) {
			if (workers != null) {
				Log.d(TAG,
						"Workers are working very hard already, please don't squeeze any more ... ");
				return;
			}
			workers = Executors.newFixedThreadPool(workerCount);
		}

		public static void shutdownNotificationWorkers() {
			if (workers != null) {
				workers.shutdown();
				workers = null;
			}
		}

		public static void tryGenerateNotification(final DatagramPacket packet) {
			workers.execute(new Runnable() {
				public void run() {
					byte[] data = packet.getData();
					if ((data[0] & 1) != 0) {
						// 确认包、回执，暂不处理
						return;
					}

					byte type = data[1];
					Log.d(TAG, "Process the Message, Message Type" + type);
					switch (type) {
					case MessageType.CALL:
						// 没有了
						break;
					case MessageType.DESTINATION:
						// 非车机，没有
						break;
					case MessageType.CONNECT:
						// Toast信息提示用户登陆成功(服务器回复的心跳信息)

						break;
					case MessageType.IDA_AUDIO:
						// 音频文件，整个文件的发，现在也没有了，
						// long msgId =
						// IntegerTransfer.byteToLong(packet.getData(),
						// 2);
						// short part =
						// IntegerTransfer.byteToShort(packet.getData(), 20);
						// FileReceiver.getInstance(msgId, part).process(packet,
						// part);
						break;
					case MessageType.IMAGE:
						// 接收图片，现在没有
						break;
					case MessageType.TAKING_PICTURE:
						// 非车机版，没有被请求拍照的功能
						break;
					case MessageType.TEAM_JOIN:
						// 有队员加入队伍
						joinTeam();
						break;
					case MessageType.TEAM_CONFIRMED:
						// 邀请被确认，现在没有了
						break;
					case MessageType.TEAM_CANCEL:
						// 队伍被队长解散，退出队伍，退出地图。现在没有了。
						break;
					case MessageType.TEAM_DESTINATION:
						// 接收到队伍的目的地
						String[] teamDestInfo = null;
						try {
							teamDestInfo = new String(packet.getData(), 2,
									packet.getLength() - 2, "UTF-8")
									.split("&&");
						} catch (UnsupportedEncodingException e1) {
							e1.printStackTrace();
						}
						if (teamDestInfo != null && teamDestInfo.length > 3)
							recevieTeamDestination(teamDestInfo[0],
									Float.parseFloat(teamDestInfo[1]),
									Float.parseFloat(teamDestInfo[2]),
									teamDestInfo[3].trim().equals("1"));
						break;
					case MessageType.TEAM_EXIT:
						// 有队友退出队伍
						exitTeam();
						break;
					case MessageType.TEAM_LOCATION:
						// 队友位置更新
						int fromUser = byteToInt(packet.getData(), 10);
						try {
							String[] info = new String(packet.getData(), 18,
									packet.getLength() - 18, "UTF-8")
									.split("&&");
							updateTeamLocation(info, fromUser);
						} catch (UnsupportedEncodingException e) {
							e.printStackTrace();
						}
						break;
					case MessageType.REAL_TIME:
						// 收到实时语音包
						final int realTimeSender = byteToInt(packet.getData(),
								10);
						byte[] audio = new byte[packet.getLength() - 18];
						System.arraycopy(packet.getData(), 18, audio, 0,
								audio.length);
						UDPSpeaker.addData(realTimeSender, audio);

						if (TeamMapActivity.instance != null) {
							try {

								if (realTimeSender != 0)
									TeamMapActivity.instance
											.showMember(realTimeSender);
								// 静音
								if (TeamMapActivity.instance.isMute())
									return;
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
						break;
					}
				}
			});
		}

		private static void updateTeamLocation(String[] info, int fromUser) {
			// info[0] Team id
			// info[1] latitude
			// info[2] longitude
			// info[3] speed
			// info[4] userName
			// info[5] direction
			// info[6] device type
			Team team = Team.getCurrentTeam();
			if (team == null || team.getMembers() == null)
				return;
			TeamMember member = team.getMembers().get(fromUser);
			if (member != null) {
				member.setLat(Float.parseFloat(info[1]));
				member.setLon(Float.parseFloat(info[2]));
				member.setSpeed(Float.parseFloat(info[3]));
				member.setDirect(Float.parseFloat(info[5]));
			}
			// 刷新队员列表
			TeamMapActivity instance = TeamMapActivity.instance;
			if (instance != null && instance.isActive()) {
				instance.getHandler().sendEmptyMessage(
						TeamMapActivity.EVENT_TEAM_UPDATE);
			}
		}

		/**
		 * Other team member confirm to join
		 */
		private static void joinTeam() {
			// 播放声音
			PlaySoundProcess.playSound(PlaySoundProcess.confirm);
			// 刷新队员列表
			TeamMapActivity instance = TeamMapActivity.instance;
			if (instance != null && instance.isActive()) {
				instance.getHandler().sendEmptyMessage(
						TeamMapActivity.EVENT_TEAM_REFRESH);
			}
		}

		/**
		 * Receive the desiniation
		 * 
		 * @param destination
		 *            the name of destination
		 * @param lat
		 *            latitude of the destination
		 * @param lon
		 *            longitude of the destination
		 * @param routeSearch
		 *            if need the search the route when setting the desination
		 */
		private static void recevieTeamDestination(String destination,
				float lat, float lon, boolean routeSearch) {
			if (Team.getCurrentTeam() != null) {
				Team.getCurrentTeam().setDestinationName(destination);
				Team.getCurrentTeam().setDestinationLat(lat);
				Team.getCurrentTeam().setDestinationLon(lon);
				Team.getCurrentTeam().setSearchRoute(routeSearch);

				PlaySoundProcess.playSound(PlaySoundProcess.confirm);
				TeamMapActivity instance = TeamMapActivity.instance;

				instance.getHandler().sendEmptyMessage(
						TeamMapActivity.EVENT_TEAM_RECEIVE_LOCATION);
			}
		}

		/**
		 * 退出队伍
		 */
		private static void exitTeam() {
			// 播放声音
			PlaySoundProcess.playSound(PlaySoundProcess.exit);

			// 刷新队员列表
			TeamMapActivity instance = TeamMapActivity.instance;
			if (instance != null && instance.isActive()) {
				instance.getHandler().sendEmptyMessage(
						TeamMapActivity.EVENT_TEAM_REFRESH);
			}
		}
	}

	private static final byte[] voice_controls = new byte[18];

	public static void sendAudioPack(ByteBuffer audioData) {
		byte[] data = new byte[548];
		ByteBuffer buffer = ByteBuffer.wrap(data);
		initControlsRuntime();
		buffer.put(voice_controls, 0, 18);
		buffer.put(audioData);
		buffer.flip();
		DatagramPacket packet = new DatagramPacket(data, audioData.limit() + 18);
		packet.setAddress(ServerAddress.udpAddress);
		packet.setPort(ServerAddress.udpPort);
		UDPServer.sendPacket(packet);
		
		System.out.println("Sending....");
	}

	public static void sendTeamLocation(BDLocation loc) {
		StringBuilder locStr = new StringBuilder();
		locStr.append(Team.getCurrentTeam().getTeamId()).append("&&");
		locStr.append(loc.getLatitude()).append("&&");
		locStr.append(loc.getLongitude()).append("&&");
		locStr.append(loc.getSpeed()).append("&&");
		locStr.append(IMDroid.getUser().getUser_name()).append("&&");
		locStr.append(loc.getDerect()).append("&&1");// 1 is device type
		byte[] payload = null;
		try {
			payload = locStr.toString().getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		byte[] dataToSend = new byte[18 + payload.length];
		dataToSend[0] |= 1 << 2; // broadcast
		dataToSend[1] = MessageType.TEAM_LOCATION;
		System.arraycopy(longToByte(System.currentTimeMillis()), 0, dataToSend,
				2, 8); // msgId
		System.arraycopy(intToByte(IMDroid.getUser().getUser_id()), 0,
				dataToSend, 10, 4); // user Id
		System.arraycopy(intToByte((int) Team.getCurrentTeam().getTeamId()), 0,
				dataToSend, 14, 4);
		System.arraycopy(payload, 0, dataToSend, 18, payload.length);
		DatagramPacket packet = new DatagramPacket(dataToSend,
				dataToSend.length);
		packet.setAddress(ServerAddress.udpAddress);
		packet.setPort(ServerAddress.udpPort);
		UDPServer.sendPacket(packet);
	}

	private static void initControlsRuntime() {
		System.arraycopy(intToByte((int) Team.getCurrentTeam().getTeamId()), 0,
				voice_controls, 14, 4); // 队伍ID
	}

	static {
		voice_controls[0] |= 1 << 1; // 大文件
		voice_controls[0] |= 1 << 2; // 广播
		// 忽略信息ID
		System.arraycopy(intToByte(IMDroid.getUser().getUser_id()), 0,
				voice_controls, 10, 4); // 发送者用户ID
		voice_controls[1] = MessageType.REAL_TIME; // 文件类型：实时语音数据
	}

}
